package com.bwton.msx.ccb.utils;

import android.text.TextUtils;
import android.util.Base64;
import android.util.Pair;

import com.bwton.msx.ccb.service.api.adapter.RxJava2CallAdapterFactory;
import com.google.gson.annotations.SerializedName;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.Url;

/**
 * @author chenliang1
 */
public class SourceManager {

    public static SourceManager getInstance() {
        return SourceManagerHolder.INSTANCE;
    }

    private DefakeService defakeService;

    private SourceManager() {
        defakeService = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                // RxJava2
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(createHttpClient())
                .build()
                .create(DefakeService.class);
    }

    public Observable<String> postReaction(String url, byte[] bytes) {
        return Observable.zip(Observable.just(url), Observable.just(bytes),
                        new BiFunction<String, byte[], Pair<String, String>>() {
                            @Override
                            public Pair<String, String> apply(String url, byte[] bytes) throws Exception {
                                return new Pair<>(url, Base64.encodeToString(bytes, Base64.NO_WRAP));
                            }
                        })
                .flatMap(new Function<Pair<String, String>, Observable<ResponseAdapter<String>>>() {
                    @Override
                    public Observable<ResponseAdapter<String>> apply(Pair<String, String> pair) throws Exception {
                        return defakeService.reaction(pair.first,
                                new MultipartBody.Builder()
                                        .setType(MultipartBody.FORM)
                                        .addFormDataPart("data", pair.second)
                                        .build());
                    }
                })
                .subscribeOn(Schedulers.io())
                .compose(new TypeTransformer<String>())
                .observeOn(AndroidSchedulers.mainThread());
    }

    private OkHttpClient createHttpClient() {
        return new OkHttpClient.Builder().build();
    }

    private static final class SourceManagerHolder {
        static final SourceManager INSTANCE = new SourceManager();
    }

    public static interface DefakeService {

        @POST
        Observable<ResponseAdapter<String>> reaction(@Url String url, @Body RequestBody defakeBody);
    }

    private class TypeTransformer<R> implements ObservableTransformer<ResponseAdapter<R>, R> {

        @Override
        public ObservableSource<R> apply(Observable<ResponseAdapter<R>> upstream) {
            return upstream.flatMap(new Function<ResponseAdapter<R>, ObservableSource<? extends R>>() {

                private final static String REQUEST_CODE_OK = "0000";

                @Override
                public ObservableSource<? extends R> apply(ResponseAdapter<R> responseAdapter) throws Exception {
                    if (TextUtils.equals(responseAdapter.code, REQUEST_CODE_OK)) {
                        return Observable.just(responseAdapter.data);
                    } else {
                        return Observable.error(new Exception("Code:" + responseAdapter.code + " Msg:" + responseAdapter.msg));
                    }
                }
            });
        }
    }

    public class ResponseAdapter<T> {
        @SerializedName("code")
        String code;

        @SerializedName("msg")
        String msg;

        @SerializedName("data")
        T data;
    }
}
